﻿goog.provide('ol.dmp.PoltEvent');
goog.provide('ol.dmp.PoltInteraction');

goog.require('goog.asserts');
goog.require('goog.events.Event');
goog.require('ol.Collection');
goog.require('ol.Coordinate');
goog.require('ol.Feature');
goog.require('ol.FeatureOverlay');
goog.require('ol.Map');
goog.require('ol.MapBrowserEvent');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.events.condition');
goog.require('ol.feature');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Pointer');
goog.require('ol.source.Vector');
goog.require('ol.dmp.algorithmsUtility');



ol.dmp.PoltEventType = {

    DRAWSTART: 'drawstart',

    DRAWEND: 'drawend'
};


ol.dmp.PoltEvent = function (type, feature)
{

    goog.base(this, type);

    /**
     * The feature being drawn.
     * @type {ol.Feature}
     * @api stable
     */
    this.feature = feature;

};
goog.inherits(ol.dmp.PoltEvent, goog.events.Event);


ol.dmp.PoltInteraction = function (options)
{

    goog.base(this);

    /**
     * @type {ol.Pixel}
     * @private
     */
    this.downPx_ = null;

    /**
     * Target source for drawn features.
     * @type {ol.source.Vector}
     * @private
     */
    this.source_ = goog.isDef(options.source) ? options.source : null;

    /**
     * Target collection for drawn features.
     * @type {ol.Collection}
     * @private
     */
    this.features_ = goog.isDef(options.features) ? options.features : null;

    /**
     * Pixel distance for snapping.
     * @type {number}
     * @private
     */
    this.snapTolerance_ = goog.isDef(options.snapTolerance) ?
        options.snapTolerance : 12;

    /**
     * The number of points that must be drawn before a polygon ring can be
     * finished.  The default is 3.
     * @type {number}
     * @private
     */
    this.minPointsPerRing_ = goog.isDef(options.minPointsPerRing) ?
        options.minPointsPerRing : 3;


    this.minPointsPerSimplyArrow_ = goog.isDef(options.minPointsPerSimplyArrow) ?
        options.minPointsPerSimplyArrow : 2;

    /**
     * Geometry type.
     * @type {ol.geom.GeometryType}
     * @private
     */
    this.type_ = options.type;

    /**
     * Drawing mode (derived from geometry type.
     * @type {ol.interaction.DrawMode}
     * @private
     */
    this.mode_ = ol.dmp.PoltInteraction.getMode_(this.type_);

    /**
     * Finish coordinate for the feature (first point for polygons, last point for
     * linestrings).
     * @type {ol.Coordinate}
     * @private
     */
    this.finishCoordinate_ = null;

    /**
     * Sketch feature.
     * @type {ol.Feature}
     * @private
     */
    this.sketchFeature_ = null;

    /**
     * Sketch point.
     * @type {ol.Feature}
     * @private
     */
    this.sketchPoint_ = null;

    /**
     * Sketch line. Used when drawing polygon.
     * @type {ol.Feature}
     * @private
     */
    this.sketchLine_ = null;

    /**
     * Sketch polygon. Used when drawing polygon.
     * @type {ol.geom.RawPolygon}
     * @private
     */
    this.sketchRawPolygon_ = null;

    /**
     * Squared tolerance for handling up events.  If the squared distance
     * between a down and up event is greater than this tolerance, up events
     * will not be handled.
     * @type {number}
     * @private
     */
    this.squaredClickTolerance_ = 4;

    /**
     * Draw overlay where are sketch features are drawn.
     * @type {ol.FeatureOverlay}
     * @private
     */
    this.overlay_ = new ol.FeatureOverlay({
        style: goog.isDef(options.style) ?
            options.style : ol.dmp.PoltInteraction.getDefaultStyleFunction()
    });

    /**
     * Name of the geometry attribute for newly created features.
     * @type {string|undefined}
     * @private
     */
    this.geometryName_ = options.geometryName;

    /**
     * @private
     * @type {ol.events.ConditionType}
     */
    this.condition_ = goog.isDef(options.condition) ?
        options.condition : ol.events.condition.noModifierKeys;

};

goog.inherits(ol.dmp.PoltInteraction, ol.interaction.Pointer);



ol.dmp.PoltInteraction.getDefaultStyleFunction = function ()
{
    var styles = ol.feature.createDefaultEditingStyles();
    return function (feature, resolution)
    {
        return styles[feature.getGeometry().getType()];
    };
};


/**
 * @inheritDoc
 */
ol.dmp.PoltInteraction.prototype.setMap = function (map)
{
    if (goog.isNull(map))
    {
        // removing from a map, clean up
        this.abortDrawing_();
    }
    this.overlay_.setMap(map);
    goog.base(this, 'setMap', map);
};


/**
 * @inheritDoc
 */
ol.dmp.PoltInteraction.prototype.handleMapBrowserEvent = function (event)
{
    var map = event.map;
    if (!map.isDef())
    {
        return true;
    }
    var pass = true;
    if (event.type === ol.MapBrowserEvent.EventType.POINTERMOVE)
    {
        pass = this.handlePointerMove_(event);
    }
    else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK)
    {
        pass = false;
    }
    return (goog.base(this, 'handleMapBrowserEvent', event) && pass);
};


/**
 * Handle down events.
 * @param {ol.MapBrowserEvent} event A down event.
 * @return {boolean} Pass the event to other interactions.
 */
ol.dmp.PoltInteraction.prototype.handlePointerDown = function (event)
{
    if (this.condition_(event))
    {
        this.downPx_ = event.pixel;
        return true;
    }
    else
    {
        return false;
    }
};


/**
 * Handle up events.
 * @param {ol.MapBrowserEvent} event An up event.
 * @return {boolean} Pass the event to other interactions.
 */
ol.dmp.PoltInteraction.prototype.handlePointerUp = function (event)
{
    var downPx = this.downPx_;
    var clickPx = event.pixel;
    var dx = downPx[0] - clickPx[0];
    var dy = downPx[1] - clickPx[1];
    var squaredDistance = dx * dx + dy * dy;
    var pass = true;
    if (squaredDistance <= this.squaredClickTolerance_)       // 判断是否是原地抬起了鼠标
    {
        this.handlePointerMove_(event);
        if (goog.isNull(this.finishCoordinate_))              // 如果 this.finishCoordinate为空，则代表新的绘制开始
        {
            this.startDrawing_(event);
        }
        else if(this.mode_ === ol.interaction.DrawMode.POINT ||
            this.atFinish_(event))              // 
        {
            this.finishDrawing_(event);
        }
        else
        {
            this.addToDrawing_(event);
        }
        pass = false;
    }
    return pass;
};


/**
 * Handle move events.
 * @param {ol.MapBrowserEvent} event A move event.
 * @return {boolean} Pass the event to other interactions.
 * @private
 */
ol.dmp.PoltInteraction.prototype.handlePointerMove_ = function (event)
{
    if (this.mode_ === ol.interaction.DrawMode.POINT &&
        goog.isNull(this.finishCoordinate_))
    {
        this.startDrawing_(event);
    }
    else if (!goog.isNull(this.finishCoordinate_))
    {
        this.modifyDrawing_(event);
    }
    else
    {
        this.createOrUpdateSketchPoint_(event);
    }
    return true;
};


/**
 * 确定一个顶点是否和此要素上的开始点重合   // 绘制多边形时用途较大  // 或者其他可以结束绘制的用途
 * Determine if an event is within the snapping tolerance of the start coord.   
 * @param {ol.MapBrowserEvent} event Event.
 * @return {boolean} The event is within the snapping tolerance of the start.
 * @private
 */
ol.dmp.PoltInteraction.prototype.atFinish_ = function (event)
{
    var at = false;
    if (!goog.isNull(this.sketchFeature_))
    {
        var geometry = this.sketchFeature_.getGeometry();
        var potentiallyDone = false;
        var potentiallyFinishCoordinates = [this.finishCoordinate_];
        if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.LineString);  // // 对直线的绘制来说，只要当前点击点和上一次点击点距离很近，那么不管时间，即认为绘制结束
            potentiallyDone = geometry.getCoordinates().length > 2;
        }
        else if (this.mode_ === ol.interaction.DrawMode.POLYGON)         // 多边形，如果点击位置在整个多边形的起点位置，那个单击即可导致绘制结束
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
            potentiallyDone = geometry.getCoordinates()[0].length >
                this.minPointsPerRing_;
            potentiallyFinishCoordinates = [this.sketchRawPolygon_[0][0],
              this.sketchRawPolygon_[0][this.sketchRawPolygon_[0].length - 2]];
        }
        else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
        {
            //potentiallyDone = geometry.getCoordinates().length > 1;
            if (this.sketchRawPolygon_[0].length == this.minPointsPerSimplyArrow_)  // 如果点个数够了，则可以认为是结束了
            {
                at = true;
            }
        }
        if (potentiallyDone)
        {
            var map = event.map;
            for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++)
            {
                var finishCoordinate = potentiallyFinishCoordinates[i];
                var finishPixel = map.getPixelFromCoordinate(finishCoordinate);
                var pixel = event.pixel;
                var dx = pixel[0] - finishPixel[0];
                var dy = pixel[1] - finishPixel[1];
                at = Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_;
                if (at)
                {
                    this.finishCoordinate_ = finishCoordinate;
                    break;
                }
            }
        }
    }
    return at;
};


/**
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.dmp.PoltInteraction.prototype.createOrUpdateSketchPoint_ = function (event)
{
    var coordinates = event.coordinate.slice();
    if (goog.isNull(this.sketchPoint_))
    {
        this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates));
        this.updateSketchFeatures_();
    }
    else
    {
        var sketchPointGeom = this.sketchPoint_.getGeometry();
        goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
        sketchPointGeom.setCoordinates(coordinates);
    }
};


/**
 * Start the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.dmp.PoltInteraction.prototype.startDrawing_ = function (event)
{
    var start = event.coordinate;
    this.finishCoordinate_ = start;
    var geometry;
    if (this.mode_ === ol.interaction.DrawMode.POINT)
    {
        geometry = new ol.geom.Point(start.slice());
    }
    else
    {
        if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
        {
            geometry = new ol.geom.LineString([start.slice(), start.slice()]);
        }
        else if (this.mode_ === ol.interaction.DrawMode.POLYGON)
        {
            this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(),
                  start.slice()]));
            this.sketchRawPolygon_ = [[start.slice(), start.slice()]];
            geometry = new ol.geom.Polygon(this.sketchRawPolygon_);
        }
        else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
        {
            this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(),start.slice()]));      
            this.sketchRawPolygon_ = [[start.slice(), start.slice()]];
            geometry = new ol.geom.Polygon(this.sketchRawPolygon_);
        }
    }
    goog.asserts.assert(goog.isDef(geometry));
    this.sketchFeature_ = new ol.Feature();
    if (goog.isDef(this.geometryName_))
    {
        this.sketchFeature_.setGeometryName(this.geometryName_);
    }
    this.sketchFeature_.setGeometry(geometry);
    this.updateSketchFeatures_();
    this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWSTART,
        this.sketchFeature_));
};


/**
 * Modify the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.dmp.PoltInteraction.prototype.modifyDrawing_ = function (event)
{
    var coordinate = event.coordinate;
    var geometry = this.sketchFeature_.getGeometry();
    var coordinates, last;
    if (this.mode_ === ol.interaction.DrawMode.POINT)
    {
        goog.asserts.assertInstanceof(geometry, ol.geom.Point);
        last = geometry.getCoordinates();
        last[0] = coordinate[0];
        last[1] = coordinate[1];
        geometry.setCoordinates(last);
    }
    else
    {
        if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
            coordinates = geometry.getCoordinates();
        }
        else if (this.mode_ === ol.interaction.DrawMode.POLYGON)
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
            coordinates = this.sketchRawPolygon_[0];
        }
        else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
            coordinates = this.sketchRawPolygon_[0];
        }
        if (this.atFinish_(event))
        {
            // snap to finish
            coordinate = this.finishCoordinate_.slice();
        }
        var sketchPointGeom = this.sketchPoint_.getGeometry();
        goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point);
        sketchPointGeom.setCoordinates(coordinate);
        last = coordinates[coordinates.length - 1];
        last[0] = coordinate[0];
        last[1] = coordinate[1];
        if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
        {
            goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
            geometry.setCoordinates(coordinates);
        }
        else if (this.mode_ === ol.interaction.DrawMode.POLYGON)
        {
            var sketchLineGeom = this.sketchLine_.getGeometry();
            goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString);
            sketchLineGeom.setCoordinates(coordinates);
            goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
            geometry.setCoordinates(this.sketchRawPolygon_);
        }
        else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
        {
            var sketchLineGeom = this.sketchLine_.getGeometry();
            goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString);
            if (coordinates.length == this.minPointsPerSimplyArrow_)
            {
                // 简单箭头的绘制 并不需要进行开始点的停靠
                coordinates.pop();
                coordinates.push(event.coordinate);
                var simplyArrwoPoints = ol.dmp.algorithmsUtility.simplyArrow(coordinates)
                sketchLineGeom.setCoordinates(simplyArrwoPoints);
                goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
                geometry.setCoordinates([simplyArrwoPoints]);
            }
            
        }
    }
    this.updateSketchFeatures_();
};


/**
 * Add a new coordinate to the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.dmp.PoltInteraction.prototype.addToDrawing_ = function (event)
{
    var coordinate = event.coordinate;
    var geometry = this.sketchFeature_.getGeometry();
    var coordinates;
    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
    {
        this.finishCoordinate_ = coordinate.slice();
        goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
        coordinates = geometry.getCoordinates();
        coordinates.push(coordinate.slice());
        geometry.setCoordinates(coordinates);
    }
    else if (this.mode_ === ol.interaction.DrawMode.POLYGON)
    {
        this.sketchRawPolygon_[0].push(coordinate.slice());
        goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
        geometry.setCoordinates(this.sketchRawPolygon_);
    }
    else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
    {
        this.sketchRawPolygon_[0].push(coordinate.slice());
        goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
        //geometry.setCoordinates(this.sketchRawPolygon_);

        //this.finishDrawing_();

        return;
    }
    this.updateSketchFeatures_();
};


/**
 * Stop drawing and add the sketch feature to the target layer.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.dmp.PoltInteraction.prototype.finishDrawing_ = function (event)
{
    var sketchFeature = this.abortDrawing_();
    goog.asserts.assert(!goog.isNull(sketchFeature));
    var coordinates;
    var geometry = sketchFeature.getGeometry();
    if (this.mode_ === ol.interaction.DrawMode.POINT)
    {
        goog.asserts.assertInstanceof(geometry, ol.geom.Point);
        coordinates = geometry.getCoordinates();
    }
    else if (this.mode_ === ol.interaction.DrawMode.LINE_STRING)
    {
        goog.asserts.assertInstanceof(geometry, ol.geom.LineString);
        coordinates = geometry.getCoordinates();
        // remove the redundant last point
        coordinates.pop();
        geometry.setCoordinates(coordinates);
    }
    else if (this.mode_ === ol.interaction.DrawMode.POLYGON)
    {
        goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
        // When we finish drawing a polygon on the last point,
        // the last coordinate is duplicated as for LineString
        // we force the replacement by the first point
        this.sketchRawPolygon_[0].pop();
        this.sketchRawPolygon_[0].push(this.sketchRawPolygon_[0][0]);
        geometry.setCoordinates(this.sketchRawPolygon_);
        coordinates = geometry.getCoordinates();
    }
    else if (this.mode_ === ol.interaction.DrawMode.SIMPLYARROW)
    {
        goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
        // When we finish drawing a polygon on the last point,
        // the last coordinate is duplicated as for LineString
        // we force the replacement by the first point
        this.sketchRawPolygon_[0].pop();
        //this.sketchRawPolygon_[0].push(event.coordinate);
        if (this.sketchRawPolygon_[0].length == this.minPointsPerSimplyArrow_)
        {
            //geometry.setCoordinates(this.sketchRawPolygon_);

            var simplyArrwoPoints = ol.dmp.algorithmsUtility.simplyArrow(this.sketchRawPolygon_[0])
            //sketchLineGeom.setCoordinates(simplyArrwoPoints);
            goog.asserts.assertInstanceof(geometry, ol.geom.Polygon);
            geometry.setCoordinates([simplyArrwoPoints]);

        }
    }

    // cast multi-part geometries
    if (this.type_ === ol.geom.GeometryType.MULTI_POINT)
    {
        sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates]));
    }
    else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING)
    {
        sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates]));
    }
    else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON)
    {
        sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
    }

    if (!goog.isNull(this.features_))
    {
        this.features_.push(sketchFeature);
    }
    if (!goog.isNull(this.source_))
    {
        this.source_.addFeature(sketchFeature);
    }
    this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWEND, sketchFeature));
};


/**
 * Stop drawing without adding the sketch feature to the target layer.
 * @return {ol.Feature} The sketch feature (or null if none).
 * @private
 */
ol.dmp.PoltInteraction.prototype.abortDrawing_ = function ()
{
    this.finishCoordinate_ = null;
    var sketchFeature = this.sketchFeature_;
    if (!goog.isNull(sketchFeature))
    {
        this.sketchFeature_ = null;
        this.sketchPoint_ = null;
        this.sketchLine_ = null;
        this.overlay_.getFeatures().clear();
    }
    return sketchFeature;
};


/**
 * Redraw the skecth features.
 * @private
 */
ol.dmp.PoltInteraction.prototype.updateSketchFeatures_ = function ()
{
    var sketchFeatures = [];
    if (!goog.isNull(this.sketchFeature_))
    {
        sketchFeatures.push(this.sketchFeature_);
    }
    if (!goog.isNull(this.sketchLine_))
    {
        sketchFeatures.push(this.sketchLine_);
    }
    if (!goog.isNull(this.sketchPoint_))
    {
        sketchFeatures.push(this.sketchPoint_);
    }
    this.overlay_.setFeatures(new ol.Collection(sketchFeatures));
};


/**
 * Get the drawing mode.  The mode for mult-part geometries is the same as for
 * their single-part cousins.
 * @param {ol.geom.GeometryType} type Geometry type.
 * @return {ol.interaction.DrawMode} Drawing mode.
 * @private
 */
ol.dmp.PoltInteraction.getMode_ = function (type)
{
    var mode;
    if (type === ol.geom.GeometryType.POINT ||
        type === ol.geom.GeometryType.MULTI_POINT)
    {
        mode = ol.interaction.DrawMode.POINT;
    }
    else if (type === ol.geom.GeometryType.LINE_STRING ||
        type === ol.geom.GeometryType.MULTI_LINE_STRING)
    {
        mode = ol.interaction.DrawMode.LINE_STRING;
    }
    else if(type === ol.geom.GeometryType.POLYGON ||
        type === ol.geom.GeometryType.MULTI_POLYGON)
    {
        mode = ol.interaction.DrawMode.POLYGON;
    }
    else if (type === ol.interaction.DrawMode.SIMPLYARROW)
    {
        mode = ol.interaction.DrawMode.SIMPLYARROW;
    }
    goog.asserts.assert(goog.isDef(mode));
    return mode;
};


/**
 * Draw mode.  This collapses multi-part geometry types with their single-part
 * cousins.
 * @enum {string}
 */
ol.interaction.DrawMode = {
    POINT: 'Point',
    LINE_STRING: 'LineString',
    POLYGON: 'Polygon',
    SIMPLYARROW:'SimplyArrow'
};